﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QD.Data.Json
{
    public delegate void JsonBuilderWriteHandle<T>(JsonBuilder json, T t);
    public enum JsonType
    {
        None,
        Object,
        Array
    }
    public class JsonBuilder
    {
        private readonly StringBuilder _sb;
        private readonly Stack<Boolean> _hasPreviousProperties;

        private bool RequiresComma { get { return _hasPreviousProperties.Count > 0 && _hasPreviousProperties.Peek(); } }

        JsonType _type = JsonType.None;
        public JsonBuilder()
        {
            _sb = new StringBuilder();
            _hasPreviousProperties = new Stack<Boolean>();
        }


        public JsonBuilder(JsonType type)
            : this()
        {
            this._type = type;
            if (this._type == JsonType.Object)
                _sb.Append("{");
            else if (this._type == JsonType.Array)
                _sb.Append("[");
        }

        public JsonBuilder WriteObject(string name, Action<JsonBuilder> buildChildren)
        {
            if (RequiresComma) _sb.Append(",");

            if (_hasPreviousProperties.Count > 0)
                _hasPreviousProperties.Pop();
            _hasPreviousProperties.Push(false);
            if (name != null)
                _sb.AppendFormat("\"{0}\":{{", name);
            else
                _sb.Append("{{");
            buildChildren(this);
            _sb.Append("}");
            if (_hasPreviousProperties.Count > 0)
                _hasPreviousProperties.Pop();
            _hasPreviousProperties.Push(true);

            return this;
        }


        public void Append(String key, Int32 value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":{1}", key, value);
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, Int64 value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":{1}", key, value);
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, Boolean value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":{1}", value.ToString().ToLower());
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, DateTime value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":\"{1}\"", value.ToString("yyyy-MM-dd hh:mm:ss fff"));
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, Double value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":{1}", key, value);
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, Decimal value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":{1}", key, value);
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key, String value)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":\"{1}\"", key, JsonCharFilter(value));
            _hasPreviousProperties.Push(true);
        }

        public void Append(String key)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":null", key);
            _hasPreviousProperties.Push(true);
        }

        public void WriteArray<T>(String key, IEnumerable<T> values, JsonBuilderWriteHandle<T> buildChildren)
        {
            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            _sb.AppendFormat("\"{0}\":", key);
            WriteArray(values, buildChildren);

        }

        public void WriteArray<T>(IEnumerable<T> values, JsonBuilderWriteHandle<T> buildChildren)
        {
            _sb.Append("[");
            _hasPreviousProperties.Push(false);
            if (values != null)
                foreach (T value in values)
                {

                    if (RequiresComma) _sb.Append(",");
                    _sb.Append("{");
                    _hasPreviousProperties.Push(false);
                    buildChildren(this, value);
                    _hasPreviousProperties.Pop();
                    _sb.Append("}");
                    _hasPreviousProperties.Push(true);
                }
            _sb.Append("]");
        }

        public void WriteArray<T>(IEnumerable<T> values)
        {

            if (RequiresComma)
            {
                _sb.Append(",");
                _hasPreviousProperties.Pop();
            }
            if (values == null) { _sb.Append("null"); return; }
            _sb.Append("[");
            TypeCode t = TypeCode.Empty;
            Type tp = typeof(T);
            if (tp == typeof(String)) t = TypeCode.String;
            else if (tp == typeof(DateTime)) t = TypeCode.DateTime;
            else if (tp == typeof(Boolean)) t = TypeCode.Boolean;

            foreach (T value in values)
            {
                if (RequiresComma)
                {
                    _sb.Append(",");
                    _hasPreviousProperties.Pop();
                }
                WriteValue(value, t);
                _hasPreviousProperties.Push(true);
            }
            _sb.Append("]");
        }

        private void WriteValue(Object val, TypeCode t)
        {

            switch (t)
            {
                case TypeCode.Boolean:
                    _sb.AppendFormat("\"{0}\"", ((Boolean)val).ToString().ToLower());
                    break;
                case TypeCode.DateTime:
                    _sb.AppendFormat("\"{0}\"", ((DateTime)val).ToString("yyyy-MM-dd hh:mm:ss fff"));
                    break;
                case TypeCode.String:
                    _sb.AppendFormat("\"{0}\"", JsonCharFilter(val.ToString()));
                    break;
                default:
                    _sb.AppendFormat("{0}", val.ToString());
                    break;
            }
        }

        public StringBuilder Raw { get { return _sb; } }

        public override string ToString()
        {
            if (this._type == JsonType.Object)
                _sb.Append("}");
            else if (this._type == JsonType.Array)
                _sb.Append("]");
            return _sb.Length == 0 ? "null" : _sb.ToString();
        }

        public string JsonCharFilter(string sourceStr)
        {
            sourceStr = sourceStr.Replace("\\", "\\\\");
            sourceStr = sourceStr.Replace("\b", "\\b");
            sourceStr = sourceStr.Replace("\t", "\\t");
            sourceStr = sourceStr.Replace("\n", "\\n");
            sourceStr = sourceStr.Replace("\v", "\\v");
            sourceStr = sourceStr.Replace("\f", "\\f");
            sourceStr = sourceStr.Replace("\r", "\\r");
            return sourceStr.Replace("\"", "\\\"");
        }



    }
}

